package drds.server.net.packet;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;

/**
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class BytesHolder {
	public static final long NULL_LENGTH = -1;
	private static final byte[] EMPTY_BYTES = new byte[0];

	private final byte[] bytes;
	private final int length;
	private int position;

	public BytesHolder(byte[] bytes) {
		this.bytes = bytes;
		this.length = bytes.length;
		this.position = 0;
	}

	public int length() {
		return length;
	}

	public int position() {
		return position;
	}

	public byte[] bytes() {
		return bytes;
	}

	public void move(int i) {
		position += i;
	}

	public void position(int i) {
		this.position = i;
	}

	public boolean hasRemaining() {
		return length > position;
	}

	public byte read(int i) {
		return bytes[i];
	}

	public byte read() {
		return bytes[position++];
	}

	public int readInt2() {
		final byte[] bytes = this.bytes;
		int $int = bytes[position++] & 0xff;
		$int |= (bytes[position++] & 0xff) << 8;
		return $int;
	}

	public int readInt3() {
		final byte[] bytes = this.bytes;
		int $int = bytes[position++] & 0xff;
		$int |= (bytes[position++] & 0xff) << 8;
		$int |= (bytes[position++] & 0xff) << 16;
		return $int;
	}

	public int readInt4() {
		final byte[] bytes = this.bytes;
		int $int = bytes[position++] & 0xff;
		$int |= (bytes[position++] & 0xff) << 8;
		$int |= (bytes[position++] & 0xff) << 16;
		$int |= (bytes[position++] & 0xff) << 24;
		return $int;
	}

	public long readLong4() {
		final byte[] bytes = this.bytes;
		long $long = (long) (bytes[position++] & 0xff);
		$long |= (long) (bytes[position++] & 0xff) << 8;
		$long |= (long) (bytes[position++] & 0xff) << 16;
		$long |= (long) (bytes[position++] & 0xff) << 24;
		return $long;
	}

	public long readLong8() {
		final byte[] bytes = this.bytes;
		long $long = (long) (bytes[position++] & 0xff);
		$long |= (long) (bytes[position++] & 0xff) << 8;
		$long |= (long) (bytes[position++] & 0xff) << 16;
		$long |= (long) (bytes[position++] & 0xff) << 24;
		$long |= (long) (bytes[position++] & 0xff) << 32;
		$long |= (long) (bytes[position++] & 0xff) << 40;
		$long |= (long) (bytes[position++] & 0xff) << 48;
		$long |= (long) (bytes[position++] & 0xff) << 56;
		return $long;
	}

	public long readLength() {
		int length = bytes[position++] & 0xff;
		switch (length) {
		case 251:
			return NULL_LENGTH;
		case 252:
			return readInt2();
		case 253:
			return readInt3();
		case 254:
			return readLong8();
		default:
			return length;
		}
	}

	public byte[] readBytes() {
		if (position >= length) {
			return EMPTY_BYTES;
		}
		byte[] $bytes = new byte[length - position];
		System.arraycopy(bytes, position, $bytes, 0, $bytes.length);
		position = length;
		return $bytes;
	}

	public byte[] readBytes(int length) {
		byte[] $bytes = new byte[length];
		System.arraycopy(bytes, position, $bytes, 0, length);
		position += length;
		return $bytes;
	}

	public byte[] readBytesWithNull() {
		final byte[] bytes = this.bytes;
		if (position >= length) {
			return EMPTY_BYTES;
		}
		int offset = -1;
		for (int i = position; i < length; i++) {
			if (bytes[i] == 0) {
				offset = i;
				break;
			}
		}
		switch (offset) {
		case -1: {
			byte[] $bytes = new byte[length - position];
			System.arraycopy(bytes, position, $bytes, 0, $bytes.length);
			position = length;
			return $bytes;
		}
		case 0:
			position++;
			return EMPTY_BYTES;
		default: {
			byte[] $bytes = new byte[offset - position];
			System.arraycopy(bytes, position, $bytes, 0, $bytes.length);
			position = offset + 1;
			return $bytes;
		}
		}
	}

	public byte[] readBytesWithLength() {
		int length = (int) readLength();
		if (length == NULL_LENGTH) {
			return null;
		}
		if (length <= 0) {
			return EMPTY_BYTES;
		}

		byte[] $bytes = new byte[length];
		System.arraycopy(bytes, position, $bytes, 0, $bytes.length);
		position += length;
		return $bytes;
	}

	public String readString() {
		if (position >= length) {
			return null;
		}
		String string = new String(bytes, position, length - position);
		position = length;
		return string;
	}

	public String readString(String charset) throws UnsupportedEncodingException {
		if (position >= length) {
			return null;
		}

		String string = new String(bytes, position, length - position, charset);
		position = length;
		return string;
	}

	public String readStringWithNull() {
		final byte[] bytes = this.bytes;
		if (position >= length) {
			return null;
		}
		int offset = -1;
		for (int i = position; i < length; i++) {
			if (bytes[i] == 0) {
				offset = i;
				break;
			}
		}
		if (offset == -1) {
			String string = new String(bytes, position, length - position);
			position = length;
			return string;
		}
		if (offset > position) {
			String string = new String(bytes, position, offset - position);
			position = offset + 1;
			return string;
		} else {
			position++;
			return null;
		}
	}

	public String readStringWithNull(String charset) throws UnsupportedEncodingException {
		final byte[] bytes = this.bytes;
		if (position >= length) {
			return null;
		}
		int offset = -1;
		for (int i = position; i < length; i++) {
			if (bytes[i] == 0) {
				offset = i;
				break;
			}
		}
		switch (offset) {
		case -1: {
			String string = new String(bytes, position, length - position, charset);
			position = length;
			return string;
		}
		case 0:
			position++;
			return null;
		default: {
			String string = new String(bytes, position, offset - position, charset);
			position = offset + 1;
			return string;
		}
		}
	}

	public String readStringWithLength() {
		int length = (int) readLength();
		if (length <= 0) {
			return null;
		}
		String string = new String(bytes, position, length);
		position += length;
		return string;
	}

	public String readStringWithLength(String charset) throws UnsupportedEncodingException {
		int length = (int) readLength();
		if (length <= 0) {
			return null;
		}
		String string = new String(bytes, position, length, charset);
		position += length;
		return string;
	}

	public java.sql.Time readTime() {
		move(6);
		int hour = read();
		int minute = read();
		int second = read();
		Calendar cal = getLocalCalendar();
		cal.set(0, 0, 0, hour, minute, second);
		return new Time(cal.getTimeInMillis());
	}

	public java.util.Date readDate() {
		byte length = read();
		int year = readInt2();
		byte month = read();
		byte date = read();
		int hour = read();
		int minute = read();
		int second = read();
		if (length == 11) {
			long nanos = readLong4();
			Calendar cal = getLocalCalendar();
			cal.set(year, --month, date, hour, minute, second);
			Timestamp time = new Timestamp(cal.getTimeInMillis());
			time.setNanos((int) nanos);
			return time;
		} else {
			Calendar cal = getLocalCalendar();
			cal.set(year, --month, date, hour, minute, second);
			return new java.sql.Date(cal.getTimeInMillis());
		}
	}

	public BigDecimal readBigDecimal() {
		String string = readStringWithLength();
		return string == null ? null : new BigDecimal(string);
	}

	private static final ThreadLocal<Calendar> localCalendar = new ThreadLocal<Calendar>();

	private static final Calendar getLocalCalendar() {
		Calendar calendar = localCalendar.get();
		if (calendar == null) {
			calendar = Calendar.getInstance();
			localCalendar.set(calendar);
		}
		return calendar;
	}

}